package org.appfuse.webapp.action;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationTrustResolver;
import org.acegisecurity.AuthenticationTrustResolverImpl;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.context.SecurityContext;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.util.MessageResources;
import org.appfuse.Constants;
import org.appfuse.model.LabelValue;
import org.appfuse.model.Role;
import org.appfuse.model.User;
import org.appfuse.service.MailEngine;
import org.appfuse.service.RoleManager;
import org.appfuse.service.UserExistsException;
import org.appfuse.service.UserManager;
import org.appfuse.util.StringUtil;
import org.appfuse.webapp.form.UserForm;
import org.appfuse.webapp.util.RequestUtil;
import org.springframework.mail.SimpleMailMessage;

/**
 * Implementation of <strong>Action</strong> that interacts with the {@link
 * UserForm} and retrieves values. It interacts with the {@link UserManager} to
 * retrieve/persist values to the database.
 * 
 * <p>
 * <a href="UserAction.java.html"><i>View Source</i></a>
 * </p>
 * 
 * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a> Modified by
 *         <a href="mailto:dan@getrolling.com">Dan Kibler</a>
 * 
 * @struts.action name="userForm" path="/users" scope="request" validate="false"
 *                parameter="method" input="mainMenu" roles="admin"
 * @struts.action name="userForm" path="/editUser" scope="request"
 *                validate="false" parameter="method" input="list" roles="admin"
 * @struts.action name="userForm" path="/editProfile" scope="request"
 *                validate="false" parameter="method" input="mainMenu"
 * @struts.action name="userForm" path="/saveUser" scope="request"
 *                validate="false" parameter="method" input="edit"
 * 
 * @struts.action-forward name="list" path="/WEB-INF/pages/userList.jsp"
 * @struts.action-forward name="edit" path="/WEB-INF/pages/userForm.jsp"
 */
public final class UserAction extends BaseAction {

	private static final List ArrayList = null;

	public ActionForward add(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'add' method");
		}
		/*
		 * rules of add users 1. sysadmin can add admin and user. 2. admin can
		 * add user. when admin add a user, remove sysadmin and admin roles from
		 * available roles in application context.
		 * 
		 */
		ensureRolesInApplicationContext(request, "add");

		User user = new User();
		user.addRole(new Role(Constants.USER_ROLE));
		UserForm userForm = (UserForm) convert(user);
		updateFormBean(mapping, request, userForm);

		checkForRememberMeLogin(request);

		return mapping.findForward("edit");
	}

	public ActionForward cancel(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'cancel' method");
		}

		return mapping.findForward("list");
	}

	public ActionForward delete(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'delete' method");
		}

		// Extract attributes and parameters we will need
		ActionMessages messages = new ActionMessages();
		UserForm userForm = (UserForm) form;

		// Exceptions are caught by ActionExceptionHandler
		UserManager mgr = (UserManager) getBean("userManager");
		User userToDelete = mgr.getUser(userForm.getUserSelected());
		String currentUserName = request.getRemoteUser();
		User currentUser = mgr.getUserByUsername(currentUserName);

		// 1.determine if is to delete sysadmin.
		if (Constants.SYSADMIN_ROLE.equals(userToDelete.getUsername())) {
			messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
					"errors.users.deletesysadmin"));
			saveErrors(request.getSession(), messages);
			return mapping.findForward("viewUsers");
		}

		// 2.determine if is to delete admin
		Set roles = userToDelete.getRoles();
		if (roles.contains(Constants.ADMIN_ROLE)) {
			if (!currentUser.getRoles().contains(Constants.SYSADMIN)) {
				messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
						"errors.users.deleteadmin"));
				return mapping.findForward("viewUsers");
			}
		}

		// 3.determine if is to current user
		String id = String.valueOf(currentUser.getId());
		String currentId = String.valueOf(userForm.getUserSelected());
		if (id.equals(currentId)) {
			messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
					"errors.user.delete.self"));
			saveErrors(request.getSession(), messages);
			return mapping.findForward("viewUsers");
		}
		User user = (User) convert(userForm);
		user.setId(Long.valueOf(userForm.getUserSelected()));
		user = mgr.getUser(String.valueOf(user.getId()));
		mgr.removeUser(userForm.getUserSelected());
		messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
				"user.deleted", userForm.getFirstName() + ' '
						+ userForm.getLastName()));

		HttpSession session = request.getSession();
		saveMessages(session, messages);

		List userList = (List) session.getAttribute(Constants.USER_LIST);
		userList.remove(user);

		// return a forward to searching users
		return mapping.findForward("list");
	}

	public ActionForward edit(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'edit' method");
		}

		ensureRolesInApplicationContext(request, "edit");
		List roles = (List) request.getSession().getServletContext()
				.getAttribute(Constants.AVAILABLE_ROLES);
		UserForm userForm = (UserForm) form;

		// if URL is "editProfile" - make sure it's the current user
		if (request.getRequestURI().indexOf("editProfile") > -1) {
			// reject if username passed in or "list" parameter passed in
			// someone that is trying this probably knows the AppFuse code
			// but it's a legitimate bug, so I'll fix it. ;-)
			if ((request.getParameter("username") != null)
					|| (request.getParameter("from") != null)) {
				response.sendError(HttpServletResponse.SC_FORBIDDEN);
				log.warn("User '" + request.getRemoteUser()
						+ "' is trying to edit user '"
						+ request.getParameter("username") + "'");

				return null;
			}
		}

		ActionMessages messages = new ActionMessages();
		UserManager mgr = (UserManager) getBean("userManager");

		User currentUser = mgr.getUserByUsername(request.getRemoteUser());
		User userToEdit = null;

		userToEdit = mgr.getUser(userForm.getUserSelected());

		/*
		 * // if a user's username is passed in if
		 * (request.getParameter("username") != null) { // lookup the user using
		 * that id userToEdit = mgr.getUserByUsername(userForm.getUsername()); }
		 * else { // look it up based on the current user's id userToEdit =
		 * mgr.getUserByUsername(request.getRemoteUser()); }
		 */

		/*
		 * edit rules as follows: 1. sysadmin can edit admin and normal users.
		 * 2. admin can edit normal users and himself/herself.
		 * 
		 */
		if (!isSysadmin(currentUser)) {
			if (isAdmin(userToEdit)
					&& !currentUser.getUsername().equals(
							userToEdit.getUsername())) {
				messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
						"errors.global.noright"));
				saveErrors(request.getSession(), messages);
				return mapping.findForward("viewUsers");
			} else if (!currentUser.getUsername().equals(
					userToEdit.getUsername())) {
				removeAdminRoleFromApplicationContext(request);
			}

		}

		// Exceptions are caught by ActionExceptionHandler
		HttpSession session = request.getSession();
		session.setAttribute("userToEdit", userToEdit);
		BeanUtils.copyProperties(userForm, convert(userToEdit));
		userForm.setConfirmPassword(userForm.getPassword());
		updateFormBean(mapping, request, userForm);

		checkForRememberMeLogin(request);

		// return a forward to edit forward
		return mapping.findForward("edit");
	}

	public ActionForward save(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'save' method");
		}
		//
		addAdminRoleToApplicationContext(request);

		// run validation rules on this form
		// See https://appfuse.dev.java.net/issues/show_bug.cgi?id=128
		ActionMessages errors = form.validate(mapping, request);

		if (!errors.isEmpty()) {
			saveErrors(request, errors);
			return mapping.findForward("edit");
		}

		// Extract attributes and parameters we will need
		ActionMessages messages = new ActionMessages();
		UserForm userForm = (UserForm) form;
		User user = new User();

		// Exceptions are caught by ActionExceptionHandler
		// all we need to persist is the parent object
		BeanUtils.copyProperties(user, userForm);

		Boolean encrypt = (Boolean) getConfiguration().get(
				Constants.ENCRYPT_PASSWORD);

		if (StringUtils.equals(request.getParameter("encryptPass"), "true")
				&& (encrypt != null && encrypt.booleanValue())) {
			String algorithm = (String) getConfiguration().get(
					Constants.ENC_ALGORITHM);

			if (algorithm == null) { // should only happen for test case
				log.debug("assuming testcase, setting algorithm to 'SHA'");
				algorithm = "SHA";
			}

			user.setPassword(StringUtil.encodePassword(user.getPassword(),
					algorithm));
		}

		UserManager mgr = (UserManager) getBean("userManager");
		RoleManager roleMgr = (RoleManager) getBean("roleManager");
		String[] userRoles = request.getParameterValues("userRoles");

		for (int i = 0; userRoles != null && i < userRoles.length; i++) {
			String roleName = userRoles[i];
			user.addRole(roleMgr.getRole(roleName));
		}
		HttpSession session = request.getSession();
		User userToEdit = (User) session.getAttribute("userToEdit");
		if (userToEdit != null) {
			user.setRecordId(userToEdit.getRecordId());
		}
		try {
			mgr.saveUser(user);
		} catch (UserExistsException e) {
			log.warn(e.getMessage());
			errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
					"errors.existing.user", userForm.getUsername(), userForm
							.getEmail()));
			saveErrors(request, errors);

			BeanUtils.copyProperties(userForm, convert(user));
			userForm.setConfirmPassword(userForm.getPassword());
			updateFormBean(mapping, request, userForm);

			return mapping.findForward("edit");
		}

		BeanUtils.copyProperties(userForm, convert(user));
		userForm.setConfirmPassword(userForm.getPassword());
		updateFormBean(mapping, request, userForm);

		if (!StringUtils.equals(request.getParameter("from"), "list")) {
			// add success messages
			messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
					"user.saved"));
			saveMessages(request.getSession(), messages);

		} else {
			// add success messages
			if ("".equals(request.getParameter("version"))) {
				messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
						"user.added", user.getFullName()));
				saveMessages(request.getSession(), messages);
				sendNewUserEmail(request, userForm);
			} else {
				messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
						"user.updated.byAdmin", user.getFullName()));
				saveMessages(request, messages);
			}
		}
		// return a forward to main Menu

		ArrayList users = (ArrayList) session.getAttribute(Constants.USER_LIST);

		User currentUser = getCurrentUser(request);
		
		if (userToEdit != null && StringUtils.equals(userToEdit.getUsername(), currentUser
				.getUsername())) {
			currentUser = loadCurrentUser(request, String.valueOf(currentUser
					.getId()));
		}
		if (users == null) {
			users = new ArrayList(0);
			session.setAttribute(Constants.USER_LIST, users);
		}

		int index = users.indexOf(user);
		if (index < 0) {
			users.add(user);
		} else {
			users.set(index, user);
		}

		return mapping.findForward("list");
	}

	public ActionForward search(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'search' method");
		}

		UserForm userForm = (UserForm) form;

		// Exceptions are caught by ActionExceptionHandler
		UserManager mgr = (UserManager) getBean("userManager");
		User user = (User) convert(userForm);
		List users = mgr.getUsers(user);
		request.getSession().setAttribute(Constants.USER_LIST, users);

		// return a forward to the user list definition
		return mapping.findForward("list");
	}

	public ActionForward update(ActionMapping mapping, ActionForm actionForm,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'update' method");
		}

		UserForm form = (UserForm) actionForm;

		// Exceptions are caught by ActionExceptionHandler
		UserManager mgr = (UserManager) getBean("userManager");
		User currentUser = getCurrentUser(request);

		form = (UserForm) convert(currentUser);
		updateFormBean(mapping, request, form);
		request.getSession().setAttribute("userToEdit", currentUser);
		// return a forward to the user list definition
		return mapping.findForward("update");
	}

	public ActionForward list(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Entering 'list' method");
		}

		// Exceptions are caught by ActionExceptionHandler
		UserManager mgr = (UserManager) getBean("userManager");
		List users = mgr.getUsers(new User());
		HttpSession session = request.getSession();
		request.getSession().setAttribute(Constants.USER_LIST, users);

		// return a forward to the user list definition
		return mapping.findForward("list");
	}

	public ActionForward unspecified(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {

		return search(mapping, form, request, response);
	}

	private void sendNewUserEmail(HttpServletRequest request, UserForm userForm)
			throws Exception {
		MessageResources resources = getResources(request);

		// Send user an e-mail
		if (log.isDebugEnabled()) {
			log.debug("Sending user '" + userForm.getUsername()
					+ "' an account information e-mail");
		}

		SimpleMailMessage message = (SimpleMailMessage) getBean("mailMessage");
		message.setTo(userForm.getFullName() + "<" + userForm.getEmail() + ">");

		StringBuffer msg = new StringBuffer();
		msg.append(resources.getMessage("newuser.email.message", userForm
				.getFullName()));
		msg.append("\n\n" + resources.getMessage("userForm.username"));
		msg.append(": " + userForm.getUsername() + "\n");
		msg.append(resources.getMessage("userForm.password") + ": ");
		msg.append(userForm.getPassword());
		msg.append("\n\nLogin at: " + RequestUtil.getAppURL(request));
		message.setText(msg.toString());

		message.setSubject(resources.getMessage("signup.email.subject"));

		MailEngine engine = (MailEngine) getBean("mailEngine");
		engine.send(message);
	}

	private void checkForRememberMeLogin(HttpServletRequest request) {
		// if user logged in with remember me, display a warning that they can't
		// change passwords
		log.debug("checking for remember me login...");

		AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
		SecurityContext ctx = SecurityContextHolder.getContext();

		if (ctx != null) {
			Authentication auth = ctx.getAuthentication();

			if (resolver.isRememberMe(auth)) {
				request.getSession().setAttribute("cookieLogin", "true");

				// add warning message
				ActionMessages messages = new ActionMessages();
				messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
						"userProfile.cookieLogin"));
				saveMessages(request, messages);
			}
		}
	}

	protected boolean isAdmin(User user) {
		if (user == null) {
			return false;
		}
		Set roles = user.getRoles();
		Iterator it = roles.iterator();
		for (; it.hasNext();) {
			Role role = (Role) it.next();
			if (Constants.ADMIN_ROLE.equals(role.getName())) {
				return true;
			}
		}
		return false;
	}

	public boolean isSysadmin(User user) {
		if (user == null) {
			return false;
		}
		Set roles = user.getRoles();
		boolean isSysadmin = false;
		Iterator it = roles.iterator();
		for (; it.hasNext();) {
			Role role = (Role) it.next();
			if (Constants.SYSADMIN_ROLE.equals(role.getName())) {
				isSysadmin = true;
			}
		}
		return isSysadmin;
	}

	protected void removeAdminRoleFromApplicationContext(
			HttpServletRequest request) {

		List availableRoles = (List) request.getSession().getServletContext()
				.getAttribute(Constants.AVAILABLE_ROLES);
		for (Iterator it = availableRoles.iterator(); it.hasNext();) {
			LabelValue role = (LabelValue) it.next();
			if (Constants.ADMIN_ROLE.equals(role.getValue())) {
				availableRoles.remove(role);
				break;
			}
		}

	}

	protected void removeSysadminRoleFromApplicationContext(
			HttpServletRequest request) {
		List availableRoles = (List) request.getSession().getServletContext()
				.getAttribute(Constants.AVAILABLE_ROLES);
		for (Iterator it = availableRoles.iterator(); it.hasNext();) {
			LabelValue role = (LabelValue) it.next();
			if (Constants.SYSADMIN_ROLE.equals(role.getValue())) {
				availableRoles.remove(role);
				break;
			}
		}
	}

	protected void addAdminRoleToApplicationContext(HttpServletRequest request) {
		List availableRoles = (List) request.getSession().getServletContext()
				.getAttribute(Constants.AVAILABLE_ROLES);
		RoleManager mgr = (RoleManager) getBean("roleManager");
		Role admin = (Role) mgr.getRole(Constants.ADMIN_ROLE);
		LabelValue lv = new LabelValue(admin.getName(), admin.getName());
		if (!availableRoles.contains(lv)) {
			availableRoles.add(lv);
		}
	}

	protected void addSysadminRoleToApplicationContext(
			HttpServletRequest request) {
		List availableRoles = (List) request.getSession().getServletContext()
				.getAttribute(Constants.AVAILABLE_ROLES);
		RoleManager mgr = (RoleManager) getBean("roleManager");
		Role sysadmin = (Role) mgr.getRole(Constants.SYSADMIN_ROLE);
		LabelValue lv = new LabelValue(sysadmin.getName(), sysadmin.getName());
		if (!availableRoles.contains(lv)) {
			availableRoles.add(lv);
		}
	}

	protected void ensureRolesInApplicationContext(HttpServletRequest request,
			String method) {

		UserManager mgr = (UserManager) getBean("userManager");
		User currentUser = (User) mgr
				.getUserByUsername(request.getRemoteUser());
		if ("edit".equalsIgnoreCase(method)) {
			if (isSysadmin(currentUser)) {
				addSysadminRoleToApplicationContext(request);
				addAdminRoleToApplicationContext(request);
			} else if (isAdmin(currentUser)) {
				addAdminRoleToApplicationContext(request);
				removeSysadminRoleFromApplicationContext(request);
			}
		} else if ("add".equalsIgnoreCase(method)) {
			if (isSysadmin(currentUser)) {
				addSysadminRoleToApplicationContext(request);
				addAdminRoleToApplicationContext(request);
			} else if (isAdmin(currentUser)) {
				removeAdminRoleFromApplicationContext(request);
				removeSysadminRoleFromApplicationContext(request);
			}
		}

	}
}
